package net.simpleframework.swing;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.text.DateFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Vector;

import javax.swing.BorderFactory;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.SpinnerDateModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;

import net.simpleframework.util.ConvertUtils;
import net.simpleframework.util.LocaleI18n;

/**
 * 这是一个开源的软件，请在LGPLv3下合法使用、修改或重新发布。
 * 
 * @author 陈侃(cknet@126.com, 13910090885)
 *         http://code.google.com/p/simpleframework/
 *         http://www.simpleframework.net
 */
public class DateTimeChooser extends OkCancelDialog {
	private static final String TITLE = LocaleI18n.getMessage("DateTimeChooser.0");

	private static final String DATE = LocaleI18n.getMessage("DateTimeChooser.1");

	private static final String TIME = LocaleI18n.getMessage("DateTimeChooser.2");

	private static final String TODAY = LocaleI18n.getMessage("DateTimeChooser.3");

	private DatePanel dp;

	private SpinnerDateModel sdm;

	private JCheckBox cb;

	private Date date;

	public DateTimeChooser(final Window owner) {
		this(owner, null);
	}

	public DateTimeChooser(final Window owner, final Date date) {
		super(owner, TITLE, date);
	}

	@Override
	protected Component createContentUI() {
		date = (Date) elements[0];
		if (date == null) {
			date = new Date();
		}
		setResizable(false);
		final JSpinner spinner = new JSpinner(sdm = new SpinnerDateModel());
		spinner.setPreferredSize(new Dimension(120, spinner.getPreferredSize().height));
		spinner.setEditor(new JSpinner.DateEditor(spinner, "hh:mm a"));
		cb = new JCheckBox(TIME);
		spinner.setEnabled(timeEnabled());
		cb.setSelected(spinner.isEnabled());
		cb.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(final ActionEvent e) {
				spinner.setEnabled(cb.isSelected());
			}
		});

		dp = new DatePanel(date);

		final JPanel p0 = new JPanel(new BorderLayout());
		p0.setBorder(BorderFactory.createTitledBorder(DATE));
		p0.add(dp);

		final JPanel retp = new JPanel(new GridBagLayout());
		final GridBagConstraints gbc = new GridBagConstraints();
		gbc.gridx = 0;
		gbc.gridy = 0;
		gbc.gridwidth = 2;
		gbc.insets = new Insets(4, 6, 4, 6);
		gbc.weightx = 1.0;
		gbc.fill = GridBagConstraints.HORIZONTAL;
		retp.add(p0, gbc);
		gbc.anchor = GridBagConstraints.WEST;
		gbc.gridwidth = 1;
		gbc.weightx = 0.0;
		gbc.fill = GridBagConstraints.NONE;
		gbc.gridy++;
		retp.add(cb, gbc);
		gbc.gridx++;
		retp.add(spinner, gbc);
		return retp;
	}

	public Date getDate() {
		return date;
	}

	protected boolean timeEnabled() {
		return true;
	}

	@Override
	public void ok() {
		final Calendar cal = Calendar.getInstance();
		cal.setTime(dp.getDate());
		if (cb.isSelected()) {
			final Calendar cal0 = Calendar.getInstance();
			cal0.setTime(sdm.getDate());
			cal.set(Calendar.HOUR, cal0.get(Calendar.HOUR));
			cal.set(Calendar.MINUTE, cal0.get(Calendar.MINUTE));
			cal.set(Calendar.AM_PM, cal0.get(Calendar.AM_PM));
		} else {
			cal.set(Calendar.HOUR, 0);
			cal.set(Calendar.MINUTE, 0);
			cal.set(Calendar.AM_PM, Calendar.AM);
		}
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		date = cal.getTime();
		super.ok();
	}

	private static final Color SELE_COLOR = new Color(0, 0, 128);

	public static final DateFormatSymbols DATE_FORMAT_SYMBOLS = new SimpleDateFormat()
			.getDateFormatSymbols();

	private static final String[] WEEKDAYS = DATE_FORMAT_SYMBOLS.getWeekdays();

	class DatePanel extends JPanel {
		private static final long serialVersionUID = 3626063035891963851L;

		private final int firstDayOfWeek = Calendar.SUNDAY;

		private final Calendar calendar = Calendar.getInstance();

		private final JLabel days[] = new JLabel[42];

		private final int yearSelectionRange = 20;

		private JPanel calendarPanel = null;

		private JComboBox monthSelector = null;
		{
			final String[] months = DATE_FORMAT_SYMBOLS.getMonths();
			final String[] dest = new String[months.length - 1];
			System.arraycopy(months, 0, dest, 0, months.length - 1);
			monthSelector = new JComboBox(dest);
			SwingUtils.initComponentHeight(monthSelector);
			monthSelector.setFont(SwingUtils.defautFont);
			monthSelector.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(final ActionEvent e) {
					calendar.set(Calendar.MONTH, monthSelector.getSelectedIndex());
					refreshDays();
				}
			});
		}

		private final JComboBox yearSelector = new JComboBox(getYears(0));
		{
			SwingUtils.initComponentHeight(yearSelector);
			yearSelector.setFont(SwingUtils.defautFont);
			yearSelector.setPreferredSize(new Dimension(100, yearSelector.getPreferredSize().height));
			yearSelector.addActionListener(new ActionListener() {
				Integer y = null;

				@Override
				public void actionPerformed(final ActionEvent e) {
					if (!refreshing) {
						y = (Integer) yearSelector.getSelectedItem();
						calendar.set(Calendar.YEAR, y.intValue());
						refreshYearSelector();
						refreshDays();
					}
				}
			});
		}

		private boolean refreshing = false;

		public DatePanel() {
			this(new Date());
		}

		public DatePanel(final Date date) {
			super();
			add(getCalendarPanel());
			setDate(date);
			calendarPanel.requestFocus();
		}

		public void setDate(Date theDate) {
			theDate = (theDate == null) ? new Date() : theDate;
			calendar.setTime(theDate);
			monthSelector.setSelectedIndex(calendar.get(Calendar.MONTH));
			refreshYearSelector();
			refreshDays();
		}

		public Date getDate() {
			return calendar.getTime();
		}

		private void refreshYearSelector() {
			if (!refreshing) {
				refreshing = true;
				yearSelector.removeAllItems();
				final Vector<?> v = getYears(calendar.get(Calendar.YEAR));
				for (final Object name2 : v) {
					yearSelector.addItem(name2);
				}
				yearSelector.setSelectedItem(new Integer(calendar.get(Calendar.YEAR)));
				refreshing = false;
			}
		}

		private Vector<Integer> getYears(final int chosenYear) {
			final Vector<Integer> v = new Vector<Integer>();
			for (int i = chosenYear - yearSelectionRange; i <= chosenYear + yearSelectionRange; i++) {
				v.addElement(new Integer(i));
			}
			return v;
		}

		private boolean equalDates(final Calendar c1, final Calendar c2) {
			if ((c1.get(Calendar.DATE) == c2.get(Calendar.DATE))
					&& (c1.get(Calendar.MONTH) == c2.get(Calendar.MONTH))
					&& (c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR))) {
				return true;
			} else {
				return false;
			}
		}

		private void refreshDays() {
			final Calendar c = getFirstVisibleDate();
			for (int i = 0; i < 42; i++) {
				days[i].setText(new Integer(c.get(Calendar.DATE)).toString());
				final Color background = equalDates(c, calendar) ? SELE_COLOR : Color.white;
				days[i].setBackground(background);
				Color foreground;
				if (equalDates(c, calendar)) {
					foreground = Color.white;
				} else if (c.get(Calendar.MONTH) == calendar.get(Calendar.MONTH)) {
					foreground = Color.black;
				} else {
					foreground = Color.lightGray;
				}
				days[i].setForeground(foreground);
				c.add(Calendar.DATE, 1);
			}
			updateUI();
		}

		private JPanel getCalendarPanel() {
			final JPanel top = new JPanel(new GridBagLayout());
			final GridBagConstraints gbc = new GridBagConstraints();
			gbc.gridx = 0;
			gbc.gridy = 0;
			gbc.insets = new Insets(0, 0, 5, 5);
			top.add(yearSelector, gbc);
			gbc.gridx++;
			gbc.weightx = 1.0;
			gbc.insets = new Insets(0, 5, 5, 0);
			gbc.fill = GridBagConstraints.HORIZONTAL;
			top.add(monthSelector, gbc);

			final JPanel dp = new JPanel(new GridLayout(7, 7));
			dp.setBackground(Color.white);
			final JLabel weeks[] = new JLabel[7];

			for (int i = 0; i < 7; i++) {
				weeks[i] = new JLabel(" " + WEEKDAYS[i + 1] + " ");
				weeks[i].setFont(SwingUtils.defautFont);
				weeks[i].setOpaque(true);
				weeks[i].setBackground(Color.gray);
				weeks[i].setForeground(Color.white);
				weeks[i].setHorizontalAlignment(SwingConstants.CENTER);
				dp.add(weeks[i]);
			}

			for (int i = 0; i < 42; i++) {
				days[i] = new JLabel();
				days[i].setName(ConvertUtils.toString(i));
				days[i].setFont(SwingUtils.defautFont);
				days[i].setOpaque(true);
				days[i].setBackground(Color.white);
				days[i].setHorizontalAlignment(SwingConstants.CENTER);
				days[i].addMouseListener(new MouseAdapter() {
					@Override
					public void mouseClicked(final MouseEvent e) {
						calendarPanel.requestFocus();
						final int i = Integer.parseInt(((JLabel) e.getSource()).getName());
						final Calendar cal = getFirstVisibleDate();
						cal.add(Calendar.DATE, i);
						setDate(cal.getTime());

						if (SwingUtilities.isLeftMouseButton(e) && (e.getClickCount() == 2)) {
							ok();
						}
					}

					@Override
					public void mouseEntered(final MouseEvent e) {
						final JLabel l = (JLabel) e.getSource();
						final Rectangle r = l.getBounds();
						final Graphics g = dp.getGraphics();
						g.setColor(Color.gray);
						g.drawRect(r.x, r.y, r.width, r.height);
					}

					@Override
					public void mouseExited(final MouseEvent me) {
						final JLabel l = (JLabel) me.getSource();
						final Graphics g = dp.getGraphics();
						g.setColor(l.getBackground());
						final Rectangle r = l.getBounds();
						g.drawRect(r.x, r.y, r.width, r.height);
					}
				});
				dp.add(days[i]);
			}

			final JLabel bottom = new JLabel();
			bottom.setFont(SwingUtils.defautFont);
			bottom.setOpaque(true);
			bottom.setBackground(Color.gray);
			bottom.setForeground(Color.white);
			bottom.setHorizontalAlignment(SwingConstants.CENTER);
			bottom.setText(TODAY + " " + ConvertUtils.toDateString(new Date()));
			bottom.addMouseListener(new MouseAdapter() {
				@Override
				public void mouseClicked(final MouseEvent e) {
					calendarPanel.requestFocus();
					if (!new Date().equals(getDate())) {
						setDate(new Date());
					}
				}
			});
			final JPanel center = new JPanel(new BorderLayout());
			center.add(dp, BorderLayout.CENTER);
			center.add(bottom, BorderLayout.SOUTH);

			calendarPanel = new JPanel(new BorderLayout());
			calendarPanel.setFocusable(true);
			calendarPanel.addKeyListener(new KeyAdapter() {
				@Override
				public void keyPressed(final KeyEvent e) {
					switch (e.getKeyCode()) {
					case KeyEvent.VK_LEFT:
						changeDate(-1);
						break;
					case KeyEvent.VK_RIGHT:
						changeDate(1);
						break;
					case KeyEvent.VK_UP:
						changeDate(-7);
						break;
					case KeyEvent.VK_DOWN:
						changeDate(7);
						break;
					}
				}
			});
			calendarPanel.add(top, BorderLayout.NORTH);
			calendarPanel.add(new JScrollPane(center), BorderLayout.CENTER);
			return calendarPanel;
		}

		private void changeDate(final int add) {
			final Calendar cal = Calendar.getInstance();
			cal.setTime(getDate());
			cal.add(Calendar.DATE, add);
			setDate(cal.getTime());
		}

		private int getFirstDayOfWeek() {
			return firstDayOfWeek;
		}

		private Calendar getFirstVisibleDate() {
			final Calendar c = Calendar.getInstance();
			c.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), 1);
			c.add(Calendar.DATE, -1);
			while (c.get(Calendar.DAY_OF_WEEK) != getFirstDayOfWeek()) {
				c.add(Calendar.DATE, -1);
			}
			return c;
		}
	}

	private static final long serialVersionUID = -3301905135605940357L;
}
